package org.geom.surface;

import java.util.ArrayList;

import org.geom.Maths;
import org.geom.Vector3D;
import org.geom.Triangle;
import org.geom.curve.ICurve;

public class TubolarSurface extends Surface {

	private ICurve curve;

	public Vector3D[] points;
	public Vector3D[] tangents;
	public Vector3D[] binormals;
	public Vector3D[] normals;

	public int size;
	private int diameter_points = 25;
	private int u = 5;

	private float tube_radius = 5;

	public TubolarSurface(ICurve curve) {
		System.out.println("TubolarSurface > .");

		this.curve = curve;
		getCurvePoints();
		sample();
	}


	private void getCurvePoints() {
		System.out.println("TubolarSurface > getCurvePoints > curve: "+getTrunk());

		int newsize = getTrunk().getPoints().size()/u;
		// reallocate
		if(size!=newsize) {
			System.out.println("TubolarSurface > reallocate everything");

			size = newsize;
			points = new Vector3D[size];
			tangents = new Vector3D[size];
			binormals = new Vector3D[size];
			normals = new Vector3D[size];

			for(int i=0;i<size; i++) {
				points[i] = new Vector3D();
			}
		}

		// updated curves points
		int j = 0;
		for(int i=0;i<getTrunk().getPoints().size()-u; i+=u) {
			points[j].x = getTrunk().getPointsWithoutResampling().get(i).x;
			points[j].y = getTrunk().getPointsWithoutResampling().get(i).y;
			points[j].z = getTrunk().getPointsWithoutResampling().get(i).z;
			j++;
		}
	}


	public void sample() {

		System.out.println("TubolarSurface > sample > dirty, resample everything ----------------------------------------");
		getCurvePoints();
		
		this.triangles = new ArrayList<Triangle>();

		//getCurveDEBUG();
		getFirstFrame();
		getTangents();
		parallelTransportFrameApproach();
		setLastFrame();
		getTube();
	}

	protected void getCurveDEBUG() {
		// points of a strange curve, circle, toroide, helix, whatever
		// parallel transport frame is a general approach
		float theta = 0.1f;
		float p = 2.4f;
		float q = 1.6f;

		float dt = ((Maths.TWO_PI) / 100)*3;

		for (int i=0; i<size; i++) {  

			//		    // circle
			//		    float x = cos(theta) * 30;
			//		    float y = sin(theta) * 30;
			//		    float z = 1;

			float _r = ((2 + Maths.cos((q * theta) / p)));
			float x = _r * Maths.cos(theta) * 30;
			float y = Maths.sin((q * theta) / p) * 30;
			float z = _r * Maths.sin(theta) * 30;

			// variants
			//    x = 20+ sin(theta)*40;
			//    y = 20+ cos(theta)*40;
			//    z = 20+ log(theta)*50;

			theta += dt;
			points[i] = new Vector3D(x,y,z);
		}

		//System.out.println(points[0]+" "+points[1]+" "+points[2]);
	}

	protected void getFirstFrame() {
		// first frame, needed by parallel transport frame approach
		// frenet method is used. more specific method (in case of complex target-oriented base animation)
		Vector3D p0, p1, b;

		p0 = points[0];
		p1 = points[1];
		tangents[0] = Vector3D.getTangentBetweenTwoPoint(p0, p1);
		p0 = points[1];
		p1 = points[2];
		tangents[1] = Vector3D.getTangentBetweenTwoPoint(p0, p1);
		b = tangents[0].cross(tangents[1]);
		binormals[0] = b;
		normals[0] = b.cross(tangents[0]);

		// invert first binormal. (pheraphs it depends on way to get the first frame)
		b.x *= -1;
		b.y *= -1;
		b.z *= -1;
		b.normalized();
	}

	protected void getTangents() {
		Vector3D p0, p1;
		for(int i=1; i<size-1; i++) { 
			// closed curve.. for last point tangent.. get the tanget between last and first point
			p0 = (Vector3D) points[i];
			p1 = (Vector3D) points[i+1];
			tangents[i%size] = Vector3D.getTangentBetweenTwoPoint(p0, p1);
		}
	}

	protected void parallelTransportFrameApproach() {
		Vector3D old_normal, p0, p1, b;
		float theta;
		int i;

		for(int j=1; j<size-1; j++) {
			i=j%size-1;

			p0 = tangents[i];
			p1 = tangents[i+1];

			// this is what is called A in game programming gems
			// and B in Hanson and Ma article
			b = p0.cross(p1);
			b.normalized();

			// normals
			//System.out.println(p0.dot(p1));
			theta = Maths.acos(p0.dot(p1));
			//System.out.println(theta);
			old_normal = normals[j-1];		
			old_normal.normalized();			
			old_normal.rotateAroundAxis(b,theta);

			old_normal.x = -old_normal.x;
			old_normal.y = -old_normal.y;
			old_normal.z = -old_normal.z;

			binormals[j] = tangents[j].cross(old_normal);
			normals[j] = tangents[j].cross(binormals[j]);
		}

		// switch last normal
		//		normals[size-1].x *= -1;
		//		normals[size-1].y *= -1;
		//		normals[size-1].z *= -1;  
	}

	protected void setLastFrame() {

	}

	protected void getTube() {
		System.out.println("TubolarSurface > getTube");
		
		Triangle t;
		Vector3D[] circle1, circle2;

		for(int i=0; i<size-2; i++) {
			circle1 = getCircle(i);
			circle2 = getCircle(i+1);

			for (int j = 0; j < diameter_points; j++) {
				t = new Triangle(circle1[j], circle2[j], circle2[j+1]);
				this.addFace(t);

				t = new Triangle(circle2[j+1], circle1[j], circle1[j+1]);
				this.addFace(t);
			}
		}
	}

	protected Vector3D[] getCircle(int i) {
		int k = diameter_points;
		float c, s;
		Vector3D vert[] = new Vector3D[k+1];
		float theta = 0;
		float dt = Maths.TWO_PI/(k);
		float radius = tube_radius ;

		for(int j=0; j<=k; j++) {
			c = Maths.cos(theta) * radius;
			s = Maths.sin(theta) * radius;

			vert[j] = new Vector3D();
			vert[j].x = points[i].x + c*binormals[i].x + s*normals[i].x;
			vert[j].y = points[i].y + c*binormals[i].y + s*normals[i].y;
			vert[j].z = points[i].z + c*binormals[i].z + s*normals[i].z;

			theta += dt;
		}  
		return vert; 
	}

	//-------------------------------------------------- 
	//-------------------------------------------------- getter %% setter
	
	public ICurve getTrunk() {
		return curve;
	}

	public void setRadius(int r) {
		tube_radius = r;
	}

	public void setDiameterPoints(int d) {
		diameter_points = d;
	}

	public void setU(int u) {
		this.u = u;
	}

	
}
